#include "ax_select.h"
#include "ax_fd.h"
#include "ax_list.h"
#include "ax_time.h"
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>

/*
 *  no thread safe now!!!
 */

static int      maxfd     = 0;
static ax_list* g_fd_list = 0;

/**
 * @brief Register a new file descriptor
 * @param fd
 * @return
 */
int ax_select_register(ax_fd* fd)
{
    if(NULL == g_fd_list)
    {
        g_fd_list = ax_list_create();
    }
    int flags;

    /* make FD nonblocking */
    flags = fcntl(fd->fd, F_GETFL);
    if(flags < 0)
        return flags;
    flags |= O_NONBLOCK;
    flags = fcntl(fd->fd, F_SETFL, flags);
    if(flags < 0)
        return flags;

    /* set close-on-exec flag */
    flags = fcntl(fd->fd, F_GETFD);
    if(flags < 0)
        return flags;
    flags |= FD_CLOEXEC;
    flags = fcntl(fd->fd, F_SETFD, flags);
    if(flags < 0)
        return flags;

    /* Register FD */
    if(fd->fd > maxfd)
        maxfd = fd->fd;

#ifdef AX_FD_CHECK
    if(ax_select_is_registered(fd))
    {
        fprintf(stderr, "Adding a ax_fd that is already in the list.\n");
        return 0;
    }
#endif
    ax_list_push_back_data(g_fd_list, fd);
    return 0;
}

/**
 * @brief Unregister a file descriptor
 * @param fd
 */
void ax_select_unregister(ax_fd* fd)
{
    if(g_fd_list == NULL)
        return;
    ax_list_del_data(g_fd_list, fd);
}

/**
 * @brief Populate the fd_sets and return the highest fd number
 * @param _rset The readfds to populate
 * @param _wset The wrtiefds to populate
 * @param _eset The errorfds to populate
 * @return The highest file descriptor seen or 0 on an empty list
 */
int ax_select_fill_fds(void* _rset, void* _wset, void* _eset)
{
    fd_set *readset = _rset, *writeset = _wset, *exceptset = _eset;
    ax_fd*  ufd;
    int     highfd = 0;

    ax_list_node* node;
    ax_list_foreach(g_fd_list, node)
    {
        ax_fd* ufd = ( ax_fd* )node->data;
        if(ufd->when & AX_FD_READ)
            FD_SET(ufd->fd, readset);

        if(ufd->when & AX_FD_WRITE)
            FD_SET(ufd->fd, writeset);

        if(ufd->when & AX_FD_EXCEPT)
            FD_SET(ufd->fd, exceptset);

        if(ufd->fd > highfd)
            highfd = ufd->fd;
    }
    return highfd;
}

int ax_select_disp_fds(void* _rset, void* _wset, void* _eset)
{
    ax_fd * ufd, *tmp;
    int     work    = 0;
    fd_set *readset = _rset, *writeset = _wset, *exceptset = _eset;

    ax_list_node* node;
    ax_list_foreach(g_fd_list, node)
    {
        ax_fd* ufd   = ( ax_fd* )node->data;
        int    flags = 0;

        if(FD_ISSET(ufd->fd, readset))
        {
            flags |= AX_FD_READ;
            FD_CLR(ufd->fd, readset);
        }

        if(FD_ISSET(ufd->fd, writeset))
        {
            flags |= AX_FD_WRITE;
            FD_CLR(ufd->fd, writeset);
        }

        if(FD_ISSET(ufd->fd, exceptset))
        {
            flags |= AX_FD_EXCEPT;
            FD_CLR(ufd->fd, exceptset);
        }

        if(flags)
        {
            work = 1;
            ufd->cb(ufd, flags);
        }
    }
    return work;
}

/**
 * @brief should run in loop
 * @param timeout_ms polling should we pollonly (1) or block on select (0)
 * @return 0 if no fd handled; 1 if fd handled; negative in case of error
 */
int ax_select_main(int timeout_ms)
{
    fd_set         readset, writeset, exceptset;
    int            rc;
    struct timeval no_time = ax_timeval_from_ms(timeout_ms);

    struct timeval* t = timeout_ms == 0 ? NULL : &no_time;

    FD_ZERO(&readset);
    FD_ZERO(&writeset);
    FD_ZERO(&exceptset);

    /* prepare read and write fdsets */
    ax_select_fill_fds(&readset, &writeset, &exceptset);

    rc = select(maxfd + 1, &readset, &writeset, &exceptset, t);
    if(rc < 0)
        return 0;

    /* call registered callback functions */
    return ax_select_disp_fds(&readset, &writeset, &exceptset);
}

/**
 * @brief find an ax_fd based on the integer fd
 * @param file descriptord
 * @return ax_fd
 */
ax_fd* ax_select_get_by_fd(int fd)
{
    ax_fd*        ofd;
    ax_list_node* node;
    ax_list_foreach(g_fd_list, node)
    {
        ax_fd* f = ( ax_fd* )node->data;
        if(f->fd == fd)
            return f;
    }
    return NULL;
}

bool ax_select_is_registered(ax_fd* fd)
{
    ax_list_node* node;
    ax_list_foreach(g_fd_list, node)
    {
        if(node->data == fd)
            return TRUE;
    }
    return FALSE;
}
